/**
 * Copyright (c) 2011, SuZhou USTC Star Information Technology CO.LTD
 * All Rights Reserved.
 */

package com.gitee.melin.bee.core.support;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import com.hubspot.jinjava.Jinjava;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 在开发的时，设置虚拟机参数-DreloadSQLFiles=true. 可以重新加载sql语句配置文件，
 * 避免修改配置文件，需要重新启动服务器。
 *
 * @author   admin@gmail.com
 */
public class CustomSQL {
	private final static Logger logger = LoggerFactory.getLogger(CustomSQL.class);

	private final Map<String, SQLBean> _sqlPool = new ConcurrentHashMap<>();

	private final Map<String, String> jinjavaTemplates = new ConcurrentHashMap<>();

	private static final String STRING_SPACE = " ";

	private DocumentBuilder documentBuilder;

	private final Map<String, Long> configMap = new HashMap<>();

	private final Jinjava jinjava = new Jinjava();

	private boolean reloadSQLFiles = false;

	private static volatile CustomSQL instance = null;

	private CustomSQL() {
	}

	public static CustomSQL getInstance() {
		if (instance == null) {
			synchronized (CustomSQL.class) {
				if (instance == null) {
					instance = new CustomSQL();
					instance.init();
				}
			}
		}

		return instance;
	}

	public void init() {
		reloadSQLFiles = Boolean.parseBoolean(System.getProperty("reloadSQLFiles"));

		try {
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			documentBuilder = documentBuilderFactory.newDocumentBuilder();

	        Resource[] configs = loadConfigs();
			for (Resource _config : configs) {
				logger.info("Loading " + _config.getURL().getPath());
				configMap.put(_config.getURL().getPath(), _config.lastModified());
				readXml(_config.getInputStream());
			}
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	protected Resource[] loadConfigs() throws IOException {
		PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
		return patternResolver.getResources("classpath*:custom-sql/**/*.xml");
	}

	public String get(String id) {
		if (reloadSQLFiles) {
			try {
				reloadConfig();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		SQLBean bean = _sqlPool.get(id);

		if (bean == null) {
			throw new IllegalStateException("sql id 不存在：" + id);
		}

		if ("simple".equals(bean.getTempateType())) {
			return _sqlPool.get(id).getContent();
		} else {
			throw new RuntimeException("SQL 模板类型不正确，只可以是simple类型");
		}
	}

	public String get(String id, Map<String, Object> content) {
		if (reloadSQLFiles) {
			try {
				reloadConfig();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if (!jinjavaTemplates.containsKey(id)) {
			throw new IllegalArgumentException("sql id 不存在：" + id);
		}
		String template = jinjavaTemplates.get(id);
		return jinjava.render(template, content);
	}

	/**
	 * 更新文件修改时间刷新。
	 */
	private void reloadConfig() throws IOException {
		Resource[] newConfigs = this.loadConfigs();
		for (Resource newConfig : newConfigs) {
			boolean flag = true;
			for (Entry<String, Long> entry : configMap.entrySet()) {
				if (newConfig.getURL().getPath().equals(entry.getKey())) {
					flag = false;

					if (newConfig.getFile().lastModified() != entry.getValue().longValue()) {
						configMap.put(entry.getKey(), newConfig.getFile().lastModified());
						readXml(newConfig.getInputStream());
						logger.info("Reloading " + entry.getKey());

						break;
					}
				}
			}

			if (flag) {
				configMap.put(newConfig.getURL().getPath(), newConfig.getFile().lastModified());
				readXml(newConfig.getInputStream());
				logger.info("Reloading " + newConfig.getURL().getPath());
			}
		}
	}

	protected synchronized void readXml(InputStream inputStream) {
		if (inputStream == null) {
			return;
		}

		Document document;
		try {
			document = documentBuilder.parse(inputStream);
			document.getDocumentElement().normalize();
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}

		NodeList list = document.getElementsByTagName("sql");
		for (int temp = 0; temp < list.getLength(); temp++) {
			Node node = list.item(temp);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				Element element = (Element) node;
				String id = element.getAttribute("id");
				String sqlType = element.getAttribute("sqlType");
				String tempateType = element.getAttribute("tempateType");

				if ("simple".equals(tempateType) || "jinja".equals(tempateType)) {
					String content = transform(element.getTextContent());

					SQLBean bean = new SQLBean();
					bean.setTempateType(tempateType);
					bean.setSqlType(sqlType);
					bean.setContent(content);

					if ("jinja".equals(tempateType)) {
						jinjavaTemplates.put(id, content);
					}

					_sqlPool.put(id, bean);
				} else {
					logger.warn("{} 对应 tempateType 值 {} 不正确，可选值为：simple 和 jinja", id, sqlType);
				}
			}
		}
	}

	protected String transform(String sql) {
		StringBuilder sb = new StringBuilder();

		try {
			BufferedReader bufferedReader =
				new BufferedReader(new StringReader(sql));

			String line = null;
			while ((line = bufferedReader.readLine()) != null) {
				sb.append(line.trim());
				sb.append(STRING_SPACE);
			}

			bufferedReader.close();
		} catch (IOException ioe) {
			return sql;
		}

		return sb.toString();
	}

	public static class SQLBean {
		/**
		 * 两种可选类型：simple和freeMarker
		 */
		private String tempateType = "simple";
		/**
		 * 两种可选类型：SQL和HQL
		 */
		private String sqlType = "SQL";
		private String content = "";

		public String getTempateType() {
			return tempateType;
		}
		public void setTempateType(String tempateType) {
			this.tempateType = tempateType;
		}
		public String getSqlType() {
			return sqlType;
		}
		public void setSqlType(String sqlType) {
			this.sqlType = sqlType;
		}
		public String getContent() {
			return content;
		}
		public void setContent(String content) {
			this.content = content;
		}

	}
}
